#pragma once
// -------------------------------------------------
// BlinKit - BlinKit Library
// -------------------------------------------------
//   File Name: script_wrappable_impl.h
// Description: ScriptWrappable Class
//      Author: Ziming Li
//     Created: 2022-03-02
// -------------------------------------------------
// Copyright (C) 2022 MingYang Software Technology.
// -------------------------------------------------

#ifndef BLINKIT_SCRIPT_WRAPPABLE_IMPL_H
#define BLINKIT_SCRIPT_WRAPPABLE_IMPL_H

#include "blinkit/blink/renderer/platform/heap/Handle.h"
#include "blinkit/blink/renderer/wtf/Noncopyable.h"
#include "third_party/quickjs/quickjs.h"

namespace BlinKit {
enum class TypeId;
}

namespace blink {

class ScriptWrappable
{
    WTF_MAKE_NONCOPYABLE(ScriptWrappable);
public:
    virtual ~ScriptWrappable(void);

    static void Initialize(void);
    static void Attach(JSRuntime *runtime);

    template <class T>
    static T* Cast(JSValue v) {
        return static_cast<T *>(CastImpl(v));
    }
    template <class T>
    static T* SafeCast(JSValue v) {
        ScriptWrappable *p = CastImpl(v);
        return p->MatchType(T::TYPE_ID) ? static_cast<T *>(p) : nullptr;
    }
protected:
    ScriptWrappable(void);

    operator JSValue() const { return JS_MKPTR(JS_TAG_OBJECT, m_ptr); }

    JSValue Wrap(JSContext *ctx);
    JSValue Wrap(JSContext *ctx, JSValue proto);

    /**
     * The atom helpers are NOT exported by QuickJS,
     * but they're very useful in the implementations
     * of GetOwnProperty.
     * 
     * These helpers SHOULD be removed once exported.
     */
    static bool AtomIsTaggedInt(JSAtom atom) {
        return (atom & JS_ATOM_TAG_INT) != 0;
    }
    static uint32_t AtomToUInt32(JSAtom atom) {
        ASSERT(AtomIsTaggedInt(atom));
        return atom & ~JS_ATOM_TAG_INT;
    }
private:
    static ScriptWrappable* CastImpl(JSValue v);

    JSValue DupValue(JSContext *ctx) const;
    bool IsEqual(JSValue v) const;

    static void ClassFinalizerCallback(JSRuntime *rt, JSValue val);
    static JSClassExoticMethods GetClassExoticMethods(void);

    virtual int GetOwnProperty(JSContext *ctx, JSPropertyDescriptor *desc, JSAtom prop);
    static int GetOwnPropertyCallback(JSContext *ctx, JSPropertyDescriptor *desc, JSValueConst obj, JSAtom prop);

#ifdef BLINKIT_UI_ENABLED
    virtual int SetProperty(JSContext *ctx, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags);
    static int SetPropertyCallback(JSContext *ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags);

    static int CallSetter(JSContext *ctx, JSValueConst obj, JSValue setter, JSAtom atom, JSValueConst value, int flags);
    static int SetPropertyByDefault(JSContext *ctx, JSValueConst obj, JSAtom atom, JSValueConst value, int flags);
#endif

    bool IsBound(void) const { return nullptr != m_ptr; }
    JSValue Bind(JSContext *ctx, JSValue scriptObject);
    virtual void OnBind(JSContext *ctx, JSValue scriptObject) {}
    virtual void OnFinalize(void) {}
    virtual BlinKit::GCObject* ObjectForGC(void)
    {
#ifndef NDEBUG
        // For debugging, all the child classes SHOULD implement ObjectForGC theirselves.
        ASSERT(false);
#endif
        return nullptr;
    }
    virtual bool MatchType(BlinKit::TypeId typeId) const;

    static constexpr uint32_t JS_ATOM_TAG_INT = (1U << 31);

    friend class ScriptController;
    void *m_ptr = nullptr;
};

} // namespace blink

#endif // BLINKIT_SCRIPT_WRAPPABLE_IMPL_H
